/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2009 - 2012 Luca Mingardi.
 *
 * This file is part of jeeObserver.
 *
 * JeeObserver is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * JeeObserver is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package jeeobserver.server;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Random;
import java.util.ResourceBundle;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import jeeobserver.logger.LoggerFormatter;

/**
 * The Class JeeObserverServerContext.
 *
 * <p>
 * Main Class of jeeObserver server. <br/>
 * Use
 * <code>createInstance</code> method to start listening for requests and
 * <code>close</close> to stop listening.
 * </p>
 *
 * @author Luca Mingardi
 * @version 4.0
 */
public class JeeObserverServerContext {

	 /** The Constant VERSION. */
	 public static final int[] VERSION = {4, 0, 0};

	 /** The Constant SERVER_PORT_PARAMETER. */
	 public static final String SERVER_PORT_PARAMETER = "JOS_SERVER_PORT";

	 /** The Constant DATABASE_HANDLER_PARAMETER. */
	 public static final String DATABASE_HANDLER_PARAMETER = "JOS_DATABASE_HANDLER";

	 /** The Constant LOGGER_LEVEL_PARAMETER. */
	 public static final String LOGGER_LEVEL_PARAMETER = "JOS_LOGGER_LEVEL";

	 /** The Constant NOTIFICATOR_TASK_NAME. */
	 public static final String NOTIFICATOR_TASK_NAME = "jeeobserverNotificatorTask";

	 /** The Constant DEFAULT_CONFIG_FILE. */
	 public static final String CONFIG_FILE = "jeeobserver-server";

	 /** The Constant DEFAULT_SERVER_PORT. */
	 public static final int DEFAULT_SERVER_PORT = 5688;

	 /** The Constant DEFAULT_DATABASE_HANDLER. */
	 public static final String DEFAULT_DATABASE_HANDLER = "jeeobserver.server.FileDatabaseHandler";

	 /** The Constant DEFAULT_LOGGER_LEVEL. */
	 public static final Level DEFAULT_LOGGER_LEVEL = Level.INFO;

	 /** The instance. */
	 private static JeeObserverServerContext instance;

	 /** The session id. */
	 private final String sessionId;

	 /** The enabled. */
	 private boolean enabled = false;

	 /** The database handler. */
	 private final DatabaseHandler databaseHandler;

	 /** The server. */
	 private final JeeObserverServer server;

	 /** The Constant logger. */
	 public static final Logger logger = JeeObserverServerContext.createLogger(JeeObserverServerContext.DEFAULT_LOGGER_LEVEL);

	 /**
	  * Instantiates a new jee observer server context.
	  *
	  * @param properties the properties
	  *
	  * @throws DatabaseException the database exception
	  * @throws JeeObserverServerException the server exception
	  */
	 private JeeObserverServerContext(int port, String databaseHandlerClassName, Map<String, String> parameters) throws JeeObserverServerException {

			this.sessionId = "JOS" + new DecimalFormat("0000000000").format(Math.abs(new Random().nextInt()));

			// Load Database Handler
			try {
				 final Class<DatabaseHandler> databaseHandlerClass = (Class<DatabaseHandler>) Class.forName(databaseHandlerClassName);

				 final Constructor<DatabaseHandler> handlerConstructor = databaseHandlerClass.getConstructor(new Class<?>[]{Map.class});
				 //final Constructor<DatabaseHandler> handlerConstructor = databaseHandlerClass.getConstructor();

				 this.databaseHandler = handlerConstructor.newInstance(new Object[]{parameters});

			} catch (final Exception  e) {
				 throw new JeeObserverServerException("Database handler loading exception.", e);
			}


			this.server = new JeeObserverServer(port);

			this.enabled = true;

			JeeObserverServerContext.logger.log(Level.INFO, "JeeObserver server context instance created.");

			JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.LOGGER_LEVEL_PARAMETER, logger.getLevel().getName()});
			JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.SERVER_PORT_PARAMETER, port});
			JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_HANDLER_PARAMETER, this.databaseHandler.getClass().getName()});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.ACTION_SAMPLING_PERIOD_PARAMETER, this.actionSamplingPeriod});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.JVM_SAMPLING_PERIOD_PARAMETER, this.jvmSamplingPeriod});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.HTTP_SESSION_SAMPLING_PERIOD_PARAMETER, this.httpSessionSamplingPeriod});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.HARD_DISK_SAMPLING_PERIOD_PARAMETER, this.hardDiskSamplingPeriod});


			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_DRIVER_PARAMETER, properties.getDatabaseDriver()});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_URL_PARAMETER, properties.getDatabaseUrl()});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_USER_PARAMETER, properties.getDatabaseUser()});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_CONNECTION_POOL_SIZE_PARAMETER, properties.getDatabaseConnectionPoolSize()});
			//JeeObserverServerContext.logger.log(Level.FINE, "JeeObserver server parameter: {0} = {1}", new Object[]{JeeObserverServerContext.DATABASE_SCHEMA_PARAMETER, properties.getDatabaseSchema()});
	 }

	 /**
	  * The main method.
	  *
	  * @param arguments the arguments
	  *
	  * @throws JeeObserverServerException the server exception
	  */
	 public static void main(String[] arguments) throws JeeObserverServerException {

			JeeObserverServerContext.logger.log(Level.INFO, "JeeObserver Server v {0}.{1}.{2}", new String[]{String.valueOf(JeeObserverServerContext.VERSION[0]), String.valueOf(JeeObserverServerContext.VERSION[1]), String.valueOf(JeeObserverServerContext.VERSION[2])});

			if ((arguments.length > 0) && (arguments[0].equals("-h") || arguments[0].equals("--help"))) {
				 JeeObserverServerContext.logger.log(Level.INFO, "");
				 JeeObserverServerContext.logger.log(Level.INFO, "Usage: JeeObserverServerContext");
				 JeeObserverServerContext.logger.log(Level.INFO, "");
				 JeeObserverServerContext.logger.log(Level.INFO, "Start jeeObserver server using parameters found on system variables or properties configuration file.");
				 JeeObserverServerContext.logger.log(Level.INFO, "");
				 JeeObserverServerContext.logger.log(Level.INFO, "Visit http:\\\\www.jeeobserver.com\\");
				 JeeObserverServerContext.logger.log(Level.INFO, "");
				 final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy");
				 JeeObserverServerContext.logger.log(Level.INFO, "Copyright 2009 - {0} Luca Mingardi.", simpleDateFormat.format(new Date()));
				 JeeObserverServerContext.logger.log(Level.INFO, "");
			} else {
				 JeeObserverServerContext.createInstance();
			}
	 }

	 /**
	  * Gets the single instance of JeeObserverServerContext.
	  *
	  * @return single instance of JeeObserverServerContext
	  */
	 public static JeeObserverServerContext getInstance() {
			if (JeeObserverServerContext.instance == null) {
				 JeeObserverServerContext.logger.log(Level.WARNING, "JeeObserver server context not yet created.");
			}

			return JeeObserverServerContext.instance;
	 }

	 /**
	  * Creates the instance.
	  *
	  * @param port
	  * @param databaseHandlerClassName
	  *
	  * @return the jee observer server context
	  *
	  * @throws JeeObserverServerException the server exception
	  */
	 public static JeeObserverServerContext createInstance(int port, String databaseHandlerClassName) throws JeeObserverServerException {
			return createInstance(port, databaseHandlerClassName, DEFAULT_LOGGER_LEVEL, null);
	 }

	 public static JeeObserverServerContext createInstance(int port, String databaseHandlerClassName, Level loggerLevel) throws JeeObserverServerException {
			return createInstance(port, databaseHandlerClassName, loggerLevel, null);
	 }

	 /**
	  * Creates the instance.
	  *
	  *
	  * @param parameters
	  *
	  * @return the jee observer server context
	  *
	  * @throws JeeObserverServerException the server exception
	  */
	 public static JeeObserverServerContext createInstance(Map<String, String> parameters) throws JeeObserverServerException {


			int port = JeeObserverServerContext.DEFAULT_SERVER_PORT;

			String databaseHandlerClassName = JeeObserverServerContext.DEFAULT_DATABASE_HANDLER;

			Level loggerLevel = JeeObserverServerContext.DEFAULT_LOGGER_LEVEL;


			//Get and check parameters

			if (parameters.containsKey(JeeObserverServerContext.LOGGER_LEVEL_PARAMETER)) {
				 try {
						JeeObserverServerContext.logger.setLevel(Level.parse(parameters.get(JeeObserverServerContext.LOGGER_LEVEL_PARAMETER)));
				 } catch (final Exception e) {
						JeeObserverServerContext.logger.log(Level.SEVERE, "Parameter " + JeeObserverServerContext.LOGGER_LEVEL_PARAMETER + " = {0} is not a valid Level. (Available values: OFF | SEVERE | WARNING | INFO | FINE | FINER | FINEST | ALL)", parameters.get(JeeObserverServerContext.LOGGER_LEVEL_PARAMETER));
						JeeObserverServerContext.logger.log(Level.FINEST, e.getMessage(), e);
				 }
			} else {
				 JeeObserverServerContext.logger.log(Level.FINE, "Parameter " + JeeObserverServerContext.LOGGER_LEVEL_PARAMETER + " not found, using default {0}.", JeeObserverServerContext.DEFAULT_LOGGER_LEVEL.getName());
			}


			if (parameters.containsKey(JeeObserverServerContext.SERVER_PORT_PARAMETER)) {
				 try {
						port = Integer.parseInt(parameters.get(JeeObserverServerContext.SERVER_PORT_PARAMETER));
				 } catch (final NumberFormatException e) {
						JeeObserverServerContext.logger.log(Level.SEVERE, "Parameter " + JeeObserverServerContext.SERVER_PORT_PARAMETER + " = {0} is not a number.", parameters.get(JeeObserverServerContext.SERVER_PORT_PARAMETER));
						JeeObserverServerContext.logger.log(Level.FINEST, e.getMessage(), e);
				 }
			} else {
				 JeeObserverServerContext.logger.log(Level.FINE, "Parameter " + JeeObserverServerContext.SERVER_PORT_PARAMETER + " not found, using default {0}.", JeeObserverServerContext.DEFAULT_SERVER_PORT);
			}

			if (parameters.containsKey(JeeObserverServerContext.DATABASE_HANDLER_PARAMETER)) {
				 databaseHandlerClassName = parameters.get(JeeObserverServerContext.DATABASE_HANDLER_PARAMETER);
			} else {
				 JeeObserverServerContext.logger.log(Level.FINE, "Parameter " + JeeObserverServerContext.DATABASE_HANDLER_PARAMETER + " not found, using default {0}.", JeeObserverServerContext.DEFAULT_DATABASE_HANDLER);
			}


			return createInstance(port, databaseHandlerClassName, loggerLevel, parameters);

	 }

	 private static JeeObserverServerContext createInstance(int port, String databaseHandlerClassName, Level loggerLevel, Map<String, String> parameters) throws JeeObserverServerException {

			JeeObserverServerContext.logger.setLevel(loggerLevel);

			final JeeObserverServerContext newInstance = new JeeObserverServerContext(port, databaseHandlerClassName, parameters);

			newInstance.getServer().start();

			Runtime.getRuntime().addShutdownHook(new Thread() {
				 @Override
				 public void run() {
						try {
							 if (JeeObserverServerContext.getInstance() != null) {
									JeeObserverServerContext.getInstance().close();
							 }
						} catch (final JeeObserverServerException e) {
							 JeeObserverServerContext.logger.log(Level.SEVERE, e.getMessage(), e);
						}
				 }
			});

			JeeObserverServerContext.instance = newInstance;

			return newInstance;
	 }

	 /**
	  * Creates the instance.
	  *
	  * @return the jee observer server context
	  *
	  * @throws JeeObserverServerException the server exception
	  */
	 public static JeeObserverServerContext createInstance() throws JeeObserverServerException {


			final Map<String, String> parameters = new HashMap<String, String>();

			// 1 - Load System variables
			for (String key : System.getenv().keySet()) {
				 String value = System.getenv().get(key);
				 if (value != null) {
						parameters.put(key, value.trim());
				 }
			}

			// 2 - Load resource Boundle
			try {

				 //Try to load resource boundle from all classLoaders using parent linkage
				 ResourceBundle resourceBundle = null;

				 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
				 do {
						try {
							 resourceBundle = ResourceBundle.getBundle(CONFIG_FILE, Locale.getDefault(), classLoader);
						} catch (MissingResourceException e) {
							 //If resource was not found, try load it from a parent classloader
							 classLoader = classLoader.getParent();
							 if (classLoader == null) {
									//If there is not parent classloader, bail out with a rethrow
									throw e;
							 }
						}
				 } while (resourceBundle == null);

				 Enumeration<String> keys = resourceBundle.getKeys();
				 while (keys.hasMoreElements()) {
						String key = keys.nextElement();
						String value = resourceBundle.getString(key);
						if (value != null) {
							 parameters.put(key, value.trim());
						}
				 }

			} catch (final MissingResourceException e) {
				 JeeObserverServerContext.logger.log(Level.FINEST, e.getMessage(), e);
			}

			return JeeObserverServerContext.createInstance(parameters);
	 }

	 /**
	  * Close.
	  *
	  * @throws JeeObserverServerException the server exception
	  */
	 public void close() throws JeeObserverServerException {

			if (this.enabled == true) {

				 this.enabled = false;

				 this.server.setEnabled(false);

				 this.server.close();

				 //TODO
				 //this.getDatabaseHandler().stopDatabase();

				 //this.startTimestamp = null;

				 JeeObserverServerContext.logger.log(Level.INFO, "JeeObserver server context instance destroyed.");
			}
	 }

	 /**
	  * Creates the logger.
	  *
	  * @param loggerLevel the logger level
	  *
	  * @return the logger
	  */
	 private static Logger createLogger(Level loggerLevel) {

			final Logger newLogger = Logger.getLogger("jeeobserver_server");

			newLogger.setUseParentHandlers(false);

			newLogger.setLevel(loggerLevel);

			final Handler handlerArray[] = newLogger.getHandlers();

			for (final Handler element : handlerArray) {
				 newLogger.removeHandler(element);
			}

			final Handler handler = new ConsoleHandler();
			handler.setLevel(Level.ALL);
			handler.setFormatter(new LoggerFormatter());

			newLogger.addHandler(handler);

			return newLogger;
	 }

	 /**
	  * Checks if is enabled.
	  *
	  * @return true, if is enabled
	  */
	 public boolean isEnabled() {
			return this.enabled;
	 }

	 public int getLocalPort() {
			return this.server.getServer().getLocalPort();

	 }

	 /**
	  * Gets the session id.
	  *
	  * @return the session id
	  */
	 public String getSessionId() {
			return this.sessionId;
	 }

	 /**
	  * Gets the database handler.
	  *
	  * @return the database handler
	  */
	 public DatabaseHandler getDatabaseHandler() {
			return this.databaseHandler;
	 }

	 /**
	  * Gets the server.
	  *
	  * @return the server
	  */
	 private JeeObserverServer getServer() {
			return this.server;
	 }
}
